/*
 * Copyright (c) Huawei Device Co., Ltd. 2024. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "ber_tlv.h"
#include "common_utils.h"

namespace OHOS {
namespace SE {
static constexpr uint32_t ONE_BYTE_LENGTH_INDEX = 2;
static constexpr uint32_t TWO_BYTES_LENGTH_INDEX = 3;
static constexpr uint32_t THREE_BYTES_LENGTH_INDEX = 4;
/*
 * Software that is part of the Secure Element access API, it obtains access
 * rules from the Secure Element and applies those rules to restrict device
 * application access to the various Secure Element applications.
 */
BerTlv::BerTlv(std::vector<uint8_t> rawData, uint32_t doTag)
{
    rawData_ = rawData;
    doTag_ = doTag;
}

BerTlv::BerTlv(std::vector<uint8_t> payload, bool isPayload, uint32_t tag)
{
    // merge to do object
    doTag_ = tag;
    switch (tag) {
        case RESPONSE_ALL_REF_AR_DO_TAG:
        case RESPONSE_REFRESH_TAG_DO:
            rawData_.push_back((tag >> ONE_BYTE_SHIFT) & ONE_BYTE_MASK);
            rawData_.push_back(tag & ONE_BYTE_MASK);
            break;
        case REF_AR_DO_TAG:
        case REF_DO_TAG:
        case AR_DO_TAG:
        case AID_REF_DO_EMPTY_TAG:
        case AID_REF_DO_TAG:
        case DEVICE_APPID_REF_DO_TAG:
        case APDU_AR_DO_TAG:
        case NFC_AR_DO_TAG:
        case PERM_AR_DO_TAG:
            rawData_.push_back(tag & ONE_BYTE_MASK);
            break;
        default :
            ErrorLog("error tag %{public}d.", tag);
    }
    uint32_t length = payload.size();
    uint32_t len_bytes = 0;
    if (length <= ONE_BYTE_LEN_MAX_NUM) {
        len_bytes = 1; // length is one byte
        rawData_.push_back(length);
    } else if (length < TWO_BYTES_LEN_MAX_NUM) {
        len_bytes = 2; // length is two bytes, one flag and one length
        rawData_.push_back(TWO_BYTES_LEN_FLAG);
        rawData_.push_back(length);
    } else if (length < THREE_BYTES_LEN_MAX_NUM) {
        len_bytes = 3; // length is three bytes, one flag and two bytes length
        rawData_.push_back(THREE_BYTES_LEN_FLAG);
        rawData_.push_back((length >> ONE_BYTE_SHIFT) & ONE_BYTE_MASK);
        rawData_.push_back(length & ONE_BYTE_MASK);
    } else {
        ErrorLog("error length %{public}d.", len_bytes);
    }
    rawData_.insert(rawData_.end(), payload.begin(), payload.end());
}

BerTlv::~BerTlv()
{
}

bool BerTlv::GetDoTag(uint32_t &tag)
{
    // tag is two bytes
    if (doTag_ == RESPONSE_ALL_REF_AR_DO_TAG || doTag_ == RESPONSE_REFRESH_TAG_DO) {
        if (rawData_.size() < TAG_LENGTH_TWO_BYTES) {
            ErrorLog("rawData_.size() %{public}lu.", rawData_.size());
            return false;
        }
        tag = (rawData_[0] << BIT_NUM_PER_BYTE) | rawData_[1];
        InfoLog("tag: 0x%{public}x doTag_: 0x%{public}x.", tag, doTag_);
        if (tag == doTag_) {
            return true;
        }
    }
    // other tag is one byte
    if (rawData_.size() < TAG_LENGTH_ONE_BYTE) {
        ErrorLog("one byte rawData_.size() %{public}lu.", rawData_.size());
        return false;
    }
    tag = rawData_[0];
    return true;
}
void BerTlv::ParseLength(const std::vector<uint8_t> &data, uint32_t &length, uint32_t &dataStartIndex)
{
    if (data[0] <= 0x80) {
        length = data[0];
        dataStartIndex = 1;
    } else if (data[0] == 0x81) {
        length = data[1];
        dataStartIndex = ONE_BYTE_LENGTH_INDEX;
    } else if (data[0] == 0x82) {
        length = ((data[1] & 0xFF) << ONE_BYTE_SHIFT) + (data[2] & 0xFF);
        dataStartIndex = TWO_BYTES_LENGTH_INDEX;
    } else if (data[0] == 0x83) {
        length = ((data[1] & 0xFF) << TWO_BYTES_SHIFT) + ((data[2] & 0xFF) << ONE_BYTE_SHIFT) + (data[3] & 0xFF);
        dataStartIndex = THREE_BYTES_LENGTH_INDEX;
    }
}
bool BerTlv::GetDoLength(uint32_t &dataStartIndex, uint32_t &length)
{
    std::vector<uint8_t> tmpData;
    uint32_t lengthStartIndex;
    // tag is two bytes
    if (doTag_ == RESPONSE_ALL_REF_AR_DO_TAG || doTag_ == RESPONSE_REFRESH_TAG_DO) {
        if (rawData_.size() <= TAG_LENGTH_TWO_BYTES) {
            ErrorLog("rawData_.size() %{public}lu, doTag_: 0x%{public}x.", rawData_.size(), doTag_);
            return false;
        }
        lengthStartIndex = TAG_LENGTH_TWO_BYTES;
    } else {
        // other tag is one byte
        if (rawData_.size() <= TAG_LENGTH_ONE_BYTE) {
            ErrorLog("one byte rawData_.size() %{public}lu, doTag_: 0x%{public}x.", rawData_.size(), doTag_);
            return false;
        }
        lengthStartIndex = TAG_LENGTH_ONE_BYTE;
    }
    tmpData.assign(rawData_.begin() + lengthStartIndex, rawData_.end());
    std::string tmpDataStr = CommonUtils::BytesArrayToHexString(&tmpData[0], tmpData.size());
    ParseLength(tmpData, length, dataStartIndex);
    dataStartIndex = dataStartIndex + lengthStartIndex;
    if (rawData_.size() < dataStartIndex + length) {
        WarnLog("%{public}s: m_rawData.size() %{public}lu, m_doTag: 0x%{public}x, length: %{public}d .",
            __func__, rawData_.size(), doTag_, length);
        // fill len:dataStartIndex + length - m_rawData.size() number 0 to rawData and not block following parse phase.
        rawData_.resize(dataStartIndex + length, 0);
    }
    return true;
}

bool BerTlv::GetDoPayload(vector<uint8_t> &payload)
{
    // tag is two bytes
    uint32_t length = 0;
    uint32_t startIndex = 0;
    if (!GetDoLength(startIndex, length) || length == 0) {
        InfoLog("length = %{public}d.", length);
        return false;
    }
    payload.assign(rawData_.begin() + startIndex, rawData_.begin() + startIndex + length);
    std::string strRawData = CommonUtils::BytesArrayToHexString(&rawData_[0], rawData_.size());
    std::string strPayload = CommonUtils::BytesArrayToHexString(&payload[0], payload.size());
    InfoLog("rawData_ %{public}s.", strRawData.c_str());
    InfoLog("length = %{public}d payload %{public}s.", length, strPayload.c_str());
    return true;
}

void BerTlv::GetRawData(std::vector<uint8_t> &rawData)
{
    rawData = rawData_;
}

}  // namespace SE
}  // namespace OHOS
